package game.util.template;

import game.util.cache.CacheListenerFun;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.LineNumberReader;
import java.io.StringReader;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import org.apache.log4j.Logger;

public class Template implements CacheListenerFun{
	private static final Logger logger = Logger.getLogger(Template.class);
	
	private BufferedReader in;

	private StringBuffer sb;

	private String FILENAME = "";
	
	public Vector itemVector;

	public Template() {
		sb = null;
		itemVector = null;
	}

	public Template(String filename) {
		this.FILENAME = filename;
		try {
			in = new BufferedReader(new FileReader(new File(filename)));
		} catch (Exception e) {
			logger.error("Constructor 1: " + e);
		}
		this.sb = new StringBuffer();

		this.itemVector = new Vector();
		this.interpret();

		try {
			in.close();
			in = null;
		} catch (Exception ex) {
			logger.error("get template error 1: " + ex);
		}
	}

	public Template(StringBuffer sb) {
		try {
			in = new BufferedReader(new LineNumberReader(new StringReader(sb
					.toString())));
		} catch (Exception e) {
			logger.error("Constructor 2: " + e);
		}

		this.sb = new StringBuffer();
		this.itemVector = new Vector();
		this.interpret();
	}

	public Enumeration elements() {
		return itemVector.elements();
	}

	public void replace(Hashtable h) {
		String tmpname;
		String tmpvalue;

		if (h.size() == 0) {
			return;
		}
		for (Enumeration e = h.keys(); e.hasMoreElements();) {
			tmpname = (String) e.nextElement();
			tmpvalue = (String) h.get(tmpname);
			replace(tmpname, tmpvalue);
		}
	}

	public boolean replace(String tag, String value) {
		boolean isReplaced = false;

		// peter: for safety
		if (tag == null)
			return false;
		if (value == null)
			value = "null";
		Item item = null;
		Enumeration en = itemVector.elements();

		while (en.hasMoreElements()) {
			item = (Item) en.nextElement();

			if ((item.getType() == Item.TAG) && (tag.equals(item.tag))) {
				item.setString(value);
				isReplaced = true;
			} else if (item.getType() == Item.LOOP) {
				item.tem.replace(tag, value);
			}
		}

		return isReplaced;
	}

	public void replaceLoop(String loopLabel, String tag, String[] value) {
		String[] tagArray = { tag };
		String[][] value2DArray = new String[value.length][1];

		for (int i = 0; i < value.length; i++)
			value2DArray[i][0] = value[i];

		replaceLoop(loopLabel, tagArray, value2DArray);
	}

	public void replaceLoop(String loopLabel, String[] tag, String[][] value) {
		Item item = null;
		Enumeration en = itemVector.elements();

		while (en.hasMoreElements()) {
			item = (Item) en.nextElement();

			if ((item.getType() == 3) && (item.getTag()).equals(loopLabel)) {
				Template newTem = new Template();
				newTem.itemVector = new Vector();
				for (int i = 0; i < value.length; i++) {
					Template tmpTem = item.tem.duplicate();
					for (int j = 0; j < tag.length; j++)
						tmpTem.replace(tag[j], value[i][j]);
					newTem.itemVector.addAll(tmpTem.itemVector);
				}

				item.tem.itemVector = newTem.itemVector;
			}
		}
	}

	public String getOutput() {
		String str = "";
		Enumeration en;
		Item item;

		for (en = elements(); en.hasMoreElements();) {
			item = (Item) en.nextElement();
			switch (item.getType()) {
			case Item.TEXT:
				str = str + item.getString();
				break;
			case Item.TAG:
				str = str + item.getString();
				break;
			case Item.LOOP:
				str = str + item.tem.getOutput();
				// str = str + item.tem.toString();
				break;
			}
		}

		return str;
	}

	// out looptag
	public String getLoopOutput() {
		String str = "";
		Enumeration en;
		Item item;

		for (en = elements(); en.hasMoreElements();) {
			item = (Item) en.nextElement();
			switch (item.getType()) {
			case Item.TEXT:
				str = str + item.getString();
				break;
			case Item.TAG:
				str = str + item.getString();
				break;
			case Item.LOOP:
				str = str + "<!--#begin~" + item.getTag() + "#-->\n"
						+ item.tem.getOutput() + "<!--#end~" + item.getTag()
						+ "#-->\n";
				break;
			}
		}

		return str;
	}

	public void flush() {
		try {
			in = new BufferedReader(new LineNumberReader(new StringReader(this
					.getOutput())));
		} catch (Exception e) {
			logger.error("Constructor 2: " + e);
		}

		this.sb = new StringBuffer();
		this.itemVector = new Vector();
		this.interpret();
	}

	// Eric: need to be extended to handle it recursively
	public String toString() {
		String str = "";
		Enumeration en;
		Item item;
		StringBuffer strbuf;

		// peter: may tune the number of init capacity
		strbuf = new StringBuffer(1000);

		for (en = elements(); en.hasMoreElements();) {
			item = (Item) en.nextElement();
			switch (item.getType()) {
			case Item.TEXT:
				// peter: here1
				// str = str + item.getString();
				strbuf.append(item.getString());
				break;
			case Item.TAG:
				String s = (String) item.getString();
				int start = 5 + s.indexOf("<!--#");
				int end = s.indexOf("#-->");
				if (start > 0 && end > 0 && start < end
						&& start < s.length() 
						&& end < s.length()) {
					strbuf.append("[");
					strbuf.append(s.substring(start, end));
					strbuf.append("]");
				} else {
					// str = str + s;
					strbuf.append(s);
				}
				break;
			case Item.LOOP:
				// str = str + item.tem.toString();
				strbuf.append(item.tem.toString());
				break;
			}
		}

		// return str;
		return strbuf.toString();
	}

	// Eric: I duplicate the attribute itemVector only
	// Later, may duplicate the other attributes as well
	public Template duplicate() {
		Template newTem = new Template();
		newTem.itemVector = new Vector();
		Item item, newItem;

		Enumeration en = itemVector.elements();
		while (en.hasMoreElements()) {
			item = (Item) en.nextElement();
			newItem = item.duplicate();
			newTem.itemVector.add(newItem);
		}

		return newTem;
	}

	private void parseAllTagInLine(String line) {
		int sPos, ePos;
		String tag, lineAfterTag;

		if ((sPos = isPatternExist(line, "<!--#")) != -1) {
			if (sPos != 0) // handle
				addItem(1, sb.toString() + line.substring(0, sPos));
			else
				addItem(1, sb.toString());

			ePos = isPatternExist(line, "#-->") + 4;
			tag = line.substring(sPos, ePos);
			addItem(2, tag);

			// initial StringBuffer for each item
			sb = new StringBuffer();

			lineAfterTag = line.substring(ePos, line.length());

			// Recursively handle all tags found in a line
			parseAllTagInLine(lineAfterTag);
		} else
			sb.append(line + "\n");
	} // parseAllTagInLine()

	private void interpret() {
		String line;
		int sPos, ePos;
		String tag = null, lineAfterTag, loopLabel;
		StringBuffer tmpSB;

		try {
			while ((line = in.readLine()) != null) {
				/* Case 3:
				 * has loop, "<!--#begin"
				 */
				if ((sPos = isPatternExist(line, "<!--#begin")) != -1) { // has
					// loop
					addItem(1, sb.toString());

					ePos = isPatternExist(line, "#-->") + 4;
					// Eric: Alert!
					// tag != loopLabel
					tag = line.substring(sPos, ePos);
					loopLabel = Item.getLoopLabel(tag);

					// initial StringBuffer for each item
					sb = new StringBuffer();
					// nested Template
					tmpSB = new StringBuffer();

					while ((isPatternExist(line = in.readLine(), "<!--#end~"
							+ loopLabel + "#-->") == -1))
						tmpSB.append(line + "\n");
					addItem(tag, tmpSB);
				} // if()

				/* Case 2:
				 * has special-tag, "<!--#tagName#-->"
				 * Parse all tags in this line recursively
				 */
				else if ((sPos = isPatternExist(line, "<!--#")) != -1) {
					parseAllTagInLine(line);
				}

				/* Case 1:
				 * plain html, no loop, no special-tag
				 */ 
				else
					sb.append(line + "\n");
			} // while()
			if (sb != null)
				addItem(1, sb.toString());

		} // end of try
		catch (Exception e) {

			logger.error("interpret: " + e);
			logger.error("interpret(tag): " + tag);
		}
	}

	private void addItem(int i, String str) {
		Item item = new Item();
		item.setType(i);

		switch (i) {
		case Item.TEXT:
			item.setString(str); // str = "<p>text</p>"
			break;

		case Item.TAG:
			item.parse(str); // str = "<!--#tagName#-->"
			item.setTag(item.argv[Item.TAGLABEL]);
			// peter: if no one replace me,
			// by default use "[" + xxx + "]"
			item.setString("<!--#" + item.getTag() + "#-->");
			break;
		}

		itemVector.add(item);
	}

	private void addItem(String tag, StringBuffer tmpSB) {
		Item item = new Item();

		item.setType(Item.LOOP);
		item.parse(tag);
		item.setTag(item.argv[Item.LOOPLABEL]);
		// create a Template obj using the 2nd constructor
		item.setTemplate(new Template(tmpSB));
		itemVector.add(item);
	}

	private int isPatternExist(String line, String pattern) {
		return (line.indexOf(pattern));
	}

	public void reinit(String s) {
		try {
			in = new BufferedReader(new StringReader(s));
		} catch (Exception e) {
			logger.error("Constructor 1: " + e);
		}

		this.sb.delete(0, sb.length());
		this.itemVector.removeAllElements();
		this.interpret();
	}

	public static void main(String argv[]) {
		Template tem;
		Enumeration en, en2;
		Item item, nestedItem;

		// for LOOP:
		String[] tag = { "name" };
		String[][] value = {
		// must match number of items in tag
				{ "Eric" }, { "Zenith" }, { "Peter" }, { "Ivor" } };

		if (argv.length == 0)
			tem = new Template("prototype.html");
		else
			tem = new Template(argv[0]);
		tem.replaceLoop("loopLabel", tag, value);

		logger.info(tem.toString());

		for (en = tem.elements(); en.hasMoreElements();) {
			item = (Item) en.nextElement();

			switch (item.getType()) {

			case Item.TEXT:
				logger.info(item.getString());
				break;

			case Item.TAG:
				logger.info(item.getString());
				break;

			case Item.LOOP:
				Template newTem = new Template();
				newTem.itemVector = new Vector();

				Template tmpTem = item.tem.duplicate();
				newTem.itemVector.addAll(tmpTem.itemVector);

				item.tem.itemVector = newTem.itemVector;

				// Template sub_tem = item.tem;
				Enumeration sub_en;
				Item sub_item;
				for (sub_en = item.tem.elements(); sub_en.hasMoreElements();) {
					sub_item = (Item) sub_en.nextElement();
					logger.info(sub_item.getString());
				}
				break;
			}
		}
	}

	public void onAbandon() {
		logger.info("Template is Abandon: filename = "+FILENAME);
	}

	public void poolClear() {
		logger.info("Template is poolCleared: filename = "+FILENAME);
	}
}
